package main

import (
	"fmt"
	"math/rand"
)

/*
*
为什么需要Goroutine池？
也就是说，有什么方案可以减缓大规模 Goroutine 对系统的调度和内存压力？要想解决问题，
最重要的是找到造成问题的根源，这个问题根源是什么？Goroutine 的数量过多导致资源侵占，
那要解决这个问题就要限制运行的 Goroutine 数量，合理复用，节省资源，具体就是 — Goroutine池化。

总结一下为什么要实现 Goroutine 池

即便每个goroutine只分配4KB的内存，但如果是恐怖如斯的数量，聚少成多，内存会占用过高。
会对GC造成极大的负担，首先GC会在回收 goroutine 上消耗性能，其次GC本身也是 goroutine ，内存吃紧的状态下连GC的调度都会出现问题。
提高响应速度，减少创建协程的时间。
更好的管理协程，控制最大并发数量，定期回收。

Goroutine池是什么？

	Goroutine 池是一个池子，里面有一些 Goroutine 。
	这个池子有一个最大容量，其内部的 Goroutine 数量不能超过其最大容量。
	可以将池子中的每个 Goroutine 看作是一个 worker ，用于执行任务。

更准确的说，Goroutine 池是一个架构。该架构由两部分组成：

	一个池子，里面有一些 Goroutine 。
	一个任务队列，里面放着给池子里的 Goroutine 执行的任务。

新来了一个任务，如果池子存满了 Goroutine ，而且它们都在工作，那么就将该任务放入任务队列，等待被处理；
如果池子没满，就新开一个 Goroutine 去处理该任务。

怎么实现Goroutine线程池?
1.Goroutine 池只是一个抽象的概念

	Golang 没有封装好的线程池
	Goroutine池只是一个概念，需要我们自己代码是有意识的实现Goroutine池.

2.Goroutine 池的设计思路

	1.启动服务的时候初始化一个Goroutine Pool ，这个协程池维护了任务的管道和worker（也就是Goroutine）
	2.外部将请求投递到Goroutine Pool,Goroutine Pool的操作是：判断当前运行的worker是否已经超过了Pool的容量，
	  如果超过就将请求放到任务管道中直到运行的worker将管道中的任务执行；如果没有超过就新开一个worker处理。

3.生产者消费者模型
在这个 投递->等待->执行 的过程中，我们很容易就想到生产者消费者模型：
生产者---（生产）--》队列--（消费任务）-》 消费者
实际上给你用来执行的goroutine 就是消费者，操作任务池的goroutine 就是生产者，而队列则可以使用go 的 buffer channel 至此，任务池的建模就到此结束了。
*/

type Job struct { //任务
	// id
	Id int
	// 需要计算的随机数
	RandNum int
}

type Result struct { //结果
	// 这里必须传对象实例
	job *Job
	// 求和
	sum int
}

func main() {
	// 需要2个管道
	// 任务管道
	jobChan := make(chan *Job, 128)
	// 结果管道
	resultChan := make(chan *Result, 128)

	// 工作池（goroutine池）
	createPool(64, jobChan, resultChan) //工作池中有64个Goroutine在工作

	// 负责打印的协程
	go func(resultChan chan *Result) {
		// 遍历结果管道，进行打印
		for result := range resultChan { //从通道resultChan接收值
			fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,
				result.job.RandNum, result.sum)
		}
	}(resultChan) //给函数传入参数，立即执行

	//主协程
	var id int
	// 循环创建job，输入到管道
	for {
		id++
		// 生成随机数
		r_num := rand.Int()
		job := &Job{
			Id:      id,
			RandNum: r_num,
		}
		jobChan <- job
	}
}

// 函数createPool：创建工作池
// 工作池里的Goroutine负责计算从jobChan取数字，然后计算各位之和，再输出到resultChan
func createPool(num int, jobChan chan *Job, resultChan chan *Result) {
	// 开 num 个协程，做计算工作
	for i := 0; i < num; i++ {
		go func(jobChan chan *Job, resultChan chan *Result) {
			// 执行运算
			// 遍历job管道所有数据，进行相加
			for job := range jobChan {
				// 随机数接过来
				r_num := job.RandNum //读取随机数
				// 随机数每一位相加
				// 定义返回值
				var sum int
				for r_num != 0 {
					tmp := r_num % 10
					sum += tmp
					r_num /= 10
				}
				// 想要的结果是Result
				r := &Result{
					job: job,
					sum: sum,
				}
				//运算结果扔到管道
				resultChan <- r
			}
		}(jobChan, resultChan)
	}
}
